using System;
using System.Net;
using System.IO;
using System.Text;
using System.Collections.Generic;

namespace MIS.EN.WebDav
{
    /// <summary>
    /// Helper class for using Exchange 2003 OWA forms-based authentication with WebDAV.  This class maintains a 
    /// cache of CookieContainer objects, so if the authentication has already been performed,
    /// then the class will grab the CookieContainer from its cache.
    /// </summary>
    public class FormsBasedAuthentication
    {
        private Dictionary<string, CookieContainer> cookieContainerCache;

        /// <summary>
        /// Public default constructor.
        /// </summary>
        public FormsBasedAuthentication()
        {
            this.cookieContainerCache = new Dictionary<string, CookieContainer>();
        }

        private static class Constants
        {
            public const string AuthenticationUrl = "{0}://{1}/CookieAuth.dll?logon";
            public const string RequestContentType = "application/x-www-form-urlencoded";
            public const string RequestMethod = "POST";
            public const string PostData = "destination=Z2FexchangeZ2F&flags=0&username={0}\\{1}&password={2}&SubmitCreds=Log On&forcedownlevel=0&trusted=0";
            public const string PostDataIsaServer2006 = "curl=Z2FexchangeZ2F&flags=0&formdir=3&username={0}\\{1}&password={2}&SubmitCreds=Log On&forcedownlevel=0&trusted=0";
            public const string HttpWebDavExceptionMessage = "There was an HTTP error trying to use forms-based authentication before the WebDAV request was sent.";
            public const string GenericWebDavExceptionMessage = "There was a general error trying to use forms-based authentication before the WebDAV request was sent.";
        }

        /// <summary>
        /// Logs in to Exchange using forms-based authentication, and grabs the necessary cookies to be used 
        /// for a WebDAV request on an Exchange server configured to use OWA forms-based authentication
        /// </summary>
        /// <param name="host">The host name of a URI.  This will typically be represented by something like this:  
        /// mail.contoso.com, with contoso being the name of a fictitious company.</param>
        /// <param name="scheme">The scheme name of a URI.  This will always typically be https.</param>
        /// <param name="credentials">The security account information that will be posted to the OWA login form.</param>
        /// <param name="useIsaServer2006">True indicates to connect to an Exchange OWA 
        /// server that is using Microsoft ISA Server 2006 in conjunction with OWA forms-based authentication.</param>
        public CookieContainer GetOwaCookies(string host, string scheme, NetworkCredential credentials, bool useIsaServer2006)
        {
            // This will hold the cookies from OWA
            CookieContainer owaCookies;

            // Build the cache key for the cookie container dictionary
            string cacheKey = scheme + host;

            // Check the cache first
            if (this.cookieContainerCache.ContainsKey(cacheKey))
            {
                owaCookies = this.cookieContainerCache[cacheKey];
            }
            else
            {
                try
                {
                    // Get a new cookie container to populate
                    owaCookies = new CookieContainer();

                    // Build the authentication URI
                    Uri serverUri = new Uri(string.Format(Constants.AuthenticationUrl, scheme, host));

                    // Create the request
                    HttpWebRequest request = WebRequest.Create(serverUri) as HttpWebRequest;

                    // Set the headers
                    request.CookieContainer = new CookieContainer();
                    request.ContentType = Constants.RequestContentType;
                    request.Method = Constants.RequestMethod;
                    request.KeepAlive = true;
                    request.AllowAutoRedirect = false;

                    // Prepare the body of the request with the OWA POST data
                    string postData = useIsaServer2006 ? Constants.PostDataIsaServer2006 : Constants.PostData;
                    byte[] body = Encoding.UTF8.GetBytes(string.Format(postData, credentials.Domain,
                        credentials.UserName, credentials.Password));

                    // Set the content length of the request
                    request.ContentLength = body.Length;

                    // Send the request to the server
                    using (Stream stream = request.GetRequestStream())
                    {
                        stream.Write(body, 0, body.Length);
                    }

                    // Get the response
                    using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
                    {
                        // Add the OWA cookies from the response to the cookie container
                        foreach (Cookie cookie in response.Cookies)
                        {
                            owaCookies.Add(new Cookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain));
                        }
                    }

                    // Add the cookie container to the cache
                    this.cookieContainerCache.Add(cacheKey, owaCookies);
                }
                catch (WebException e)
                {
                    // Re-throw the exception with a wrapper
                    throw new WebDavException(Constants.HttpWebDavExceptionMessage, e);
                }
                catch (Exception e)
                {
                    // Re-throw the exception with a wrapper
                    throw new WebDavException(Constants.GenericWebDavExceptionMessage, e);
                }
            }
            return owaCookies;
        }
    }
}
